#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <iostream>

#include "script/include/squirrel.h"
#include "script/include/sqstdio.h"
#include "script/include/sqstdaux.h"
#include "script/include/sqstdblob.h"
#include "script/include/sqstdmath.h"
#include "script/include/sqstdstring.h"
#include "script/include/sqstdsystem.h"

#ifdef _MSC_VER
//#pragma comment (lib ,"squirrel.lib")
//#pragma comment (lib ,"sqstdlib.lib")
#endif

#ifdef SQUNICODE

#define scvprintf vfwprintf
#else

#define scvprintf vfprintf
#endif

#include "script.hpp"
#include "debug.hpp"

namespace lge2d
{

#define MAIN_SCRIPT_OBJ "_main_script_obj"
#define INIT_FILE_NAME "init.nut"

SQInteger print_args(HSQUIRRELVM v);

void printfunc(HSQUIRRELVM v, const SQChar *s, ...)
{
	char buff[2048] =
	{	0};
	va_list vl;
	va_start(vl, s);
	vsnprintf(buff, 2048, s, vl);
	va_end(vl);

	sq_pushroottable(v);
		sq_pushstring(v, MAIN_SCRIPT_OBJ, strlen(MAIN_SCRIPT_OBJ));
		sq_get(v, -2);
		lge2d::Script * Script = 0;
		sq_getuserpointer(v, -1, (reinterpret_cast<void**>(&Script)));
		assert(NULL != Script);
		sq_pop(v, 2);// remove userpointer and root table
		Script->Print(buff);
	}

void errorfunc(HSQUIRRELVM v, const SQChar *s, ...)
{
	SQChar buff[2048] =
	{	0};
	va_list vl;
	va_start(vl, s);
	vsnprintf(buff, 2048, s, vl);
	va_end(vl);

	sq_pushroottable(v);
		sq_pushstring(v, MAIN_SCRIPT_OBJ, strlen(MAIN_SCRIPT_OBJ));
		sq_get(v, -2);// table, string(key)
		lge2d::Script * Script = 0;
		sq_getuserpointer(v, -1, (reinterpret_cast<void**>(&Script)));
		assert(NULL != Script);
		sq_pop(v, 2);// remove userpointer and root table
		Script->Print(buff);
	}

void call_foo(HSQUIRRELVM v, int n, float f, const SQChar *s)
{
	SQInteger top = sq_gettop(v); //saves the stack size before the call
	sq_pushroottable(v); //pushes the global table
	sq_pushstring(v, _SC("foo"), -1);
	if (SQ_SUCCEEDED(sq_get(v,-2)))
	{ //gets the field 'foo' from the global table
		sq_pushroottable(v);
		sq_pushinteger(v, n);
		sq_pushfloat(v, f);
		sq_pushstring(v, s, -1);
		sq_call(v, 4, SQFalse, SQTrue); //calls the function
	}
	sq_settop(v, top); //restores the original stack size
}

Script::Script()
{
	v = sq_open(1024); // creates a VM with initial stack size 1024
	//REGISTRATION OF STDLIB
	//push the root table where the std function will be registered
	sq_pushroottable(v);
	sqstd_register_iolib(v); //registers a library
	sqstd_register_stringlib(v);
	sqstd_register_mathlib(v);
	sqstd_register_bloblib(v);
	sqstd_register_systemlib(v);
	sq_pop(v, 1); //pops the root table
	//END REGISTRATION OF STDLIB

	sqstd_seterrorhandlers(v); //registers the default error handlers

	sq_setprintfunc(v, printfunc, errorfunc); //sets the print function

	sq_pushroottable(v);

	sq_pushstring(v, MAIN_SCRIPT_OBJ, strlen(MAIN_SCRIPT_OBJ));
	sq_pushuserpointer(v, reinterpret_cast<void*>(this));
	// store pointer to Script Object in vm for print error
	sq_newslot(v, -3, /*static*/SQFalse);

	if (!SQ_SUCCEEDED(sqstd_dofile(v, _SC(INIT_FILE_NAME), SQFalse, SQTrue)))
	{
		std::clog << "can't do file: " << INIT_FILE_NAME << std::endl;
	}
}

Script::~Script()
{
	sq_pop(v, 1); //pops the root table
	sq_close(v);
}

SQInteger print_args(HSQUIRRELVM v)
{
	SQInteger nargs = sq_gettop(v); //number of arguments
	for (SQInteger n = 1; n <= nargs; n++)
	{
		if (n > 1)
		{
			printf(" arg %d is ", n);
		}
		else
		{
			printf("arg %d is ", n);
		}
		switch (sq_gettype(v, n))
		{
		case OT_NULL:
			printf("null");
			break;
		case OT_INTEGER:
			printf("integer");
			break;
		case OT_FLOAT:
			printf("float");
			break;
		case OT_STRING:
			printf("string");
			break;
		case OT_TABLE:
			printf("table");
			break;
		case OT_ARRAY:
			printf("array");
			break;
		case OT_USERDATA:
			printf("userdata");
			break;
		case OT_CLOSURE:
			printf("closure(function)");
			break;
		case OT_NATIVECLOSURE:
			printf("native closure(C function)");
			break;
		case OT_GENERATOR:
			printf("generator");
			break;
		case OT_USERPOINTER:
			printf("userpointer");
			break;
		case OT_CLASS:
			printf("class");
			break;
		case OT_INSTANCE:
			printf("instance");
			break;
		case OT_WEAKREF:
			printf("weak reference");
			break;
		default:
			return sq_throwerror(v, "invalid param"); //throws an exception
		}
	}
	printf("\n");
	fflush(stdout);
	//sq_pushinteger(v, nargs); //push the number of arguments as return value
	return 0; //1 because 1 value is returned
}

std::string Script::Execute(const char * script)
{
	Print(script);
	if (SQ_SUCCEEDED(sq_compilebuffer(v, (SQChar*)script,
					strlen(script), "console", SQTrue)))
	{
		sq_pushroottable(v);
		sq_call(v, /*params count*/1, /*retval*/SQFalse, /*raiseerror*/SQTrue);
		sq_pop(v, 1); // pops clousure
	}
	std::string out_msg;
	out_msg = m_print_ss.str();
	return out_msg;
}

void Script::Print(const std::string& message)
{
	m_print_ss << std::endl << message;
}

int Script::GetGlobal(const char* str_key, int default_value)
{
	int result = 0;
	assert(NULL != str_key);
	//push the root table(were the globals of the script will be stored)
	sq_pushroottable(v);
	sq_pushstring(v, str_key, strlen(str_key));
	if (!SQ_SUCCEEDED(sq_get(v, -2))) // table, string(key)
	{
		result = default_value;
	}
	else
	{
		if (!SQ_SUCCEEDED(sq_getinteger(v, -1, &result)))
		{
			result = default_value;
		}
		sq_pop(v, 1);
	}
	sq_pop(v, 1); // remove int and root table
	return result;
}
float Script::GetGlobal(const char* str_key, float default_value)
{
	assert(NULL != str_key);
	sq_pushroottable(v);
	sq_pushstring(v, str_key, strlen(str_key));
	float result = 0;
	if (!SQ_SUCCEEDED(sq_get(v, -2))) // table, string(key)
	{
		result = default_value;
	}
	else
	{
		if (!SQ_SUCCEEDED(sq_getfloat(v, -1, &result)))
		{
			result = default_value;
		}
		sq_pop(v, 1);
	}
	sq_pop(v, 1); // remove int and root table
	return result;
}
std::string Script::GetGlobal(const char* str_key,
		const std::string& default_value)
{
	assert(NULL != str_key);
	sq_pushroottable(v);
	sq_pushstring(v, str_key, strlen(str_key));
	const char* result = NULL;
	if (!SQ_SUCCEEDED(sq_get(v, -2))) // table, string(key)
	{
		result = default_value.c_str();
	}
	else
	{
		if (!SQ_SUCCEEDED(sq_getstring(v, -1, &result)))
		{
			result = default_value.c_str();
		}
		sq_pop(v, 1);
	}
	sq_pop(v, 1); // root table
	return result;
}

} // namespace lge2d
